package cn.zwx.learn.basic.redis_lock;

import cn.hutool.core.collection.CollectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**  continue one's life
 * @description: 续命线程
 * @projectName:java-basic
 * @see:cn.zwx.learn.basic.redis_lock
 * @author:zhangwenxue
 * @createTime:2021/3/24 10:02
 * @version:1.0
 */
public class ContinueLifeThead implements Runnable{

    private Logger logger = LoggerFactory.getLogger(ContinueLifeThead.class);
    /**
     * @description RedisKey
     **/
    private final String key;
    /**
     * @description RedisValue
     **/
    private final String value;
    /**
     * @description 占用锁的时间 单位毫秒
     **/
    private Long lockTime;

    /**
     * @description redisCommands对象
     **/
    private RedisTemplate redisTemplate;

    private TimeUnit timeUnit;

    /**
     * @description 续命线程是否在运行
     **/
    boolean isOk = Boolean.TRUE;

    /**
     * @description 续命Redis锁Lua脚本
     **/
    public static final String UNLOCK_LUA;

    /**
     * 续命脚本，原子操作
     */
    static {
        //  if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1],ARGV[2]) else return 0 end
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"GET\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"EXPIRE\",KEYS[1],ARGV[2]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }



    public ContinueLifeThead(String key, String value, Long lockTime,TimeUnit timeUnit, RedisTemplate redisTemplate) {
        this.key = key;
        this.value = value;
        this.lockTime = lockTime;
        this.redisTemplate = redisTemplate;
        this.timeUnit = timeUnit;
    }

    @Override
    public void run() {
        while (isOk) {
            try {
                //让线程休息会，别老占用资源 在执行时间超过2/3时去执行续费
                timeUnit.sleep(Math.multiplyExact(Math.floorDiv(lockTime,3),2));
                //KEYS 参数
                List<String> keys = CollectionUtil.newArrayList();
                keys.add(key);
                //ARGV 参数
                Object[] values = new Object[]{value, String.valueOf(lockTime)};
                //这里采用 lua 脚本 “续费锁” ，注意要续费是自己持有的锁， value 值唯一确认现在这把锁是自己持有的
                DefaultRedisScript<Boolean> rs = new DefaultRedisScript<>();
                //设置脚本
                rs.setScriptText(UNLOCK_LUA);
                //定义返回类型。注意如果没有这个定义，spring不会返回结果
                rs.setResultType(Boolean.class);
                RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
                //定义key
                List<String> keyList = new ArrayList<String>();
                keyList.add(key);
                Boolean execute = (Boolean)  redisTemplate.execute(rs,stringRedisSerializer,
                        stringRedisSerializer,keyList,value,String.valueOf(lockTime));
                if (execute) {
                    logger.info("续费成功key:{},value:{}，将锁超时时间重置为 " + lockTime + "s",keys,values);
                } else {
                    stop();
                    logger.info("续费失败 key:{},value:{}",keys,values);
                }

            } catch (InterruptedException ignored) {
            } catch (Exception e) {
                logger.error("续费线程异常",e);
            }
        }
        logger.info("续费结束");
    }

    public void stop() {
        this.isOk = false;
    }


}
